{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "provenance": [],
      "include_colab_link": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/nceder/qpb4e/blob/main/code/Chapter%2016/Chapter_16.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "# 16.1 What is a regular expression?"
      ],
      "metadata": {
        "id": "mToh-jUG4yG3"
      }
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "6rOG-8nYrAfv",
        "outputId": "05b98d8c-328d-4751-fee0-33ab6a7d4eb9",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "source": [
        "open(\"textfile\", 'w').write(\"\"\"this is a file with hello\n",
        "on 2 of 3 lines\n",
        "hello.\"\"\")\n",
        "\n",
        "import re\n",
        "regexp = re.compile(\"hello\")\n",
        "count = 0\n",
        "file = open(\"textfile\", 'r')\n",
        "for line in file.readlines():\n",
        "    if regexp.search(line):\n",
        "        count = count + 1\n",
        "file.close()\n",
        "print(count)"
      ],
      "execution_count": 1,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "2\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "# 16.3 Regular expressions and raw strings\n",
        "\n",
        "## 16.3.1 Raw strings to the rescue"
      ],
      "metadata": {
        "id": "9kkhWypw47Zf"
      }
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "6QIpxWdLrAfw",
        "outputId": "4bf6e6d8-25ab-438f-d7f2-098266d04cbe",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "source": [
        "r\"Hello\" == \"Hello\""
      ],
      "execution_count": 2,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "True"
            ]
          },
          "metadata": {},
          "execution_count": 2
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "SdwfViT5rAfx",
        "outputId": "8e53104f-e0c1-48f5-eee8-237a424bd137",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "source": [
        "r\"\\the\" == \"\\\\the\""
      ],
      "execution_count": 3,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "True"
            ]
          },
          "metadata": {},
          "execution_count": 3
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "oTFmlYxprAfx",
        "outputId": "182381f0-c971-4427-9d2e-e0a188289298",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "source": [
        "r\"\\the\" == \"\\the\""
      ],
      "execution_count": 4,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "False"
            ]
          },
          "metadata": {},
          "execution_count": 4
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "PzVfZ0mFrAfx",
        "outputId": "8880667e-67d1-4712-c138-2c725284498f",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "source": [
        "print(r\"\\the\")"
      ],
      "execution_count": 5,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\\the\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "Ztx09Gi4rAfx",
        "outputId": "6613f5f5-f3ef-42c4-ec84-ae6c181a70e8",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "source": [
        "print(\"\\the\")"
      ],
      "execution_count": 6,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\the\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "# 16.4 Extracting matched text from strings"
      ],
      "metadata": {
        "id": "xM39JzkQ576K"
      }
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "q735tm1OrAfx",
        "outputId": "ed813245-dfcb-471b-9757-3380e925cf72",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "source": [
        "open(\"textfile\", 'w').write(\"\"\"Lopez, Maria: 111-555-2222\n",
        "Jackson, Fred: 555-3333\"\"\")\n",
        "\n",
        "import re\n",
        "regexp = re.compile(r\"[-a-zA-Z]+,\"\n",
        "                    r\" [-a-zA-Z]+\"\n",
        "                    r\"( [-a-zA-Z]+)?\"\n",
        "                    r\": (\\d{3}-)?\\d{3}-\\d{4}\"\n",
        "                   )\n",
        "file = open(\"textfile\", 'r')\n",
        "for line in file.readlines():\n",
        "    if regexp.search(line):\n",
        "        print(\"Yeah, I found a line with a name and number. So what?\")\n",
        "        break\n",
        "file.close()"
      ],
      "execution_count": 7,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Yeah, I found a line with a name and number. So what?\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "A0o5Vm_7rAfx",
        "outputId": "5e8a35ed-99fd-4d27-cf59-181b79b41a15",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "source": [
        "import re\n",
        "regexp = re.compile(r\"(?P<last>[-a-zA-Z]+),\"\n",
        "                    r\" (?P<first>[-a-zA-Z]+)\"\n",
        "                    r\"( (?P<middle>([-a-zA-Z]+)))?\"\n",
        "                    r\": (?P<phone>((\\d{3}-)?\\d{3}-\\d{4}))\"\n",
        "                   )\n",
        "file = open(\"textfile\", 'r')\n",
        "for line in file.readlines():\n",
        "    result = regexp.search(line)\n",
        "    if result == None:\n",
        "        print(\"Oops, I don't think this is a record\")\n",
        "    else:\n",
        "        last_name = result.group('last')\n",
        "        first_name = result.group('first')\n",
        "        middle_name = result.group('middle')\n",
        "        if middle_name == None:\n",
        "            \tmiddle_name = \"\"\n",
        "        phone_number = result.group('phone')\n",
        "        print(f\"Name: {first_name} {middle_name} {last_name} Number: {phone_number}\")\n",
        "file.close()"
      ],
      "execution_count": 8,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Name: Maria  Lopez Number: 111-555-2222\n",
            "Name: Fred  Jackson Number: 555-3333\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Try This: Extracting matched text\n",
        "\n",
        "Making international calls usually requires a + and the country code. Assuming that the country code is two digits, how would you modify the code above to extract the + and the country code as part of the number? (Again, not all numbers have a country code.) How would you make the code handle country codes of one to three digits?"
      ],
      "metadata": {
        "id": "XNPix2N--8FD"
      }
    },
    {
      "cell_type": "code",
      "source": [],
      "metadata": {
        "id": "L6jr1sx2_CsD"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "# @title\n",
        "print(re.match(r\": (?P<phone>(\\+\\d{2}-)?(\\d\\d\\d-)?\\d\\d\\d-\\d\\d\\d\\d)\", \": +01-111-222-3333\"))\n",
        "\n",
        "# or\n",
        "print(re.match(r\": (?P<phone>(\\+\\d{2}-)?(\\d{3}-)?\\d{3}-\\d{4})\", \": +01-111-222-3333\"))\n",
        "\n",
        "# or\n",
        "# For one- to three-digit country codes:\n",
        "print(re.match(r\": (?P<phone>(\\+\\d{1,3}-)?(\\d{3}-)?\\d{3}-\\d{4})\", \": +011-111-222-3333\"))"
      ],
      "metadata": {
        "cellView": "form",
        "id": "4Irrxz1F_DIl"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "# 16.5 Substituting text with regular expressions"
      ],
      "metadata": {
        "id": "zBmzFhze-jMv"
      }
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "hIAPoPnnrAfy",
        "outputId": "19788711-eee2-42f2-b317-48389f13ec9a",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 35
        }
      },
      "source": [
        "import re\n",
        "string = \"If the the problem is textual, use the the re module\"\n",
        "pattern = r\"the the\"\n",
        "regexp = re.compile(pattern)\n",
        "regexp.sub(\"the\", string)"
      ],
      "execution_count": 9,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "'If the problem is textual, use the re module'"
            ],
            "application/vnd.google.colaboratory.intrinsic+json": {
              "type": "string"
            }
          },
          "metadata": {},
          "execution_count": 9
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 16.5.1 Using a function with sub"
      ],
      "metadata": {
        "id": "2kRT6UzyAfSw"
      }
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "Rjbfa-wJrAfy",
        "outputId": "6c7d1403-a1a1-422e-8946-e98ef83fbcce",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 35
        }
      },
      "source": [
        "import re\n",
        "int_string = \"1 2 3 4 5\"\n",
        "def int_match_to_float(match_obj):\n",
        "    return(match_obj.group('num') + \".0\")\n",
        "\n",
        "pattern = r\"(?P<num>[0-9]+)\"\n",
        "regexp = re.compile(pattern)\n",
        "regexp.sub(int_match_to_float, int_string)"
      ],
      "execution_count": 10,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "'1.0 2.0 3.0 4.0 5.0'"
            ],
            "application/vnd.google.colaboratory.intrinsic+json": {
              "type": "string"
            }
          },
          "metadata": {},
          "execution_count": 10
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "import re\n",
        "int_string = \"1 2 3 4 5 Python\"\n",
        "def int_match_to_float(match_obj):\n",
        "    return(match_obj.group('num') + \".0\")\n",
        "\n",
        "pattern = r\"(?P<num>[0-9]+)\"\n",
        "regexp = re.compile(pattern)\n",
        "regexp.sub(int_match_to_float, int_string)"
      ],
      "metadata": {
        "id": "_Rdwqe1w99KM",
        "outputId": "d8165099-fed8-421d-f114-4a9803b80e19",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 35
        }
      },
      "execution_count": 11,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "'1.0 2.0 3.0 4.0 5.0 Python'"
            ],
            "application/vnd.google.colaboratory.intrinsic+json": {
              "type": "string"
            }
          },
          "metadata": {},
          "execution_count": 11
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Try This: Replacing text\n",
        "\n",
        "In the checkpoint in section 16.4, you extended a phone-number regular expression to also recognize a country code. How would you use a function to make any numbers that didn't have a country code now have +1 (the country code for the United States and Canada)?"
      ],
      "metadata": {
        "id": "FIo6y08C-vxT"
      }
    },
    {
      "cell_type": "code",
      "source": [],
      "metadata": {
        "id": "2Q1uX0-RWfVn"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "# @title\n",
        "def add_code(match_obj):\n",
        "    return(\"+1 \"+match_obj.group('phone'))\n",
        "\n",
        "re.sub(r\"(?P<phone>(\\d{3}-)?\\d{3}-\\d{4})\", add_code, \"111-222-3333\")"
      ],
      "metadata": {
        "id": "Qmc7i0Pw-r6W",
        "cellView": "form"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "# 16.6 Lab 16: Phone-Number normalizer\n",
        "\n",
        "In the United States and Canada, phone numbers consist of ten digits, usually separated into a three-digit area code, a three-digit exchange code, and a four-digit station code. As mentioned in section 16.4, they may or may not be preceded by +1, the country code. In practice, however, you have many ways to format a phone number, such as (NNN) NNN-NNNN, NNN-NNN-NNNN, NNN NNN-NNNN, NNN.NNN.NNNN, and NNN NNN NNNN, to name a few. Also, the country code may not be present, may not have a +, and usually (not always) is separated from the number by a space or dash. Whew!\n",
        "\n",
        "In this lab, your task is to create a phone-number normalizer that takes any of the formats and returns a normalized phone number 1-NNN-NNN-NNNN.\n",
        "\n",
        "The following are all possible phone numbers:\n",
        "```\n",
        "+1 223-456-7890\n",
        "1-223-456-7890\n",
        "+1 223 456-7890\n",
        "(223) 456-7890\n",
        "1 223 456 7890\n",
        "223.456.7890\n",
        "```\n",
        "\n",
        "**Bonus:** The first digit of the area code and the exchange code can only be 2-9, and the second digit of an area code can't be 9. Use this information to validate the input and return a `ValueError` exception of invalid phone number if the number is invalid.\n"
      ],
      "metadata": {
        "id": "JSzolTaVAn-f"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Author's solution"
      ],
      "metadata": {
        "id": "kqaGf7KJBdR5"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "import re\n",
        "\n",
        "def return_number(match_obj):\n",
        "    # adds country if needed\n",
        "    country = match_obj.group(\"country\")\n",
        "    if not country:\n",
        "        country = \"1\"\n",
        "\n",
        "    # BONUS: raise ValueError if not valid\n",
        "    if not re.match(r\"[2-9][0-8]\\d\", match_obj.group(\"area\") ):\n",
        "        raise ValueError(\"invalid phone number area code {}\".format(match_obj.group(\"area\")))\n",
        "    if not re.match(r\"[2-9]\\d\\d\", match_obj.group(\"exch\") ):\n",
        "        raise ValueError(\"invalid phone number exchange {}\".format(match_obj.group(\"exch\")))\n",
        "\n",
        "    return(f\"{country}-{match_obj.group('area')}-{match_obj.group('exch')}-{match_obj.group('number')}\")\n",
        "\n",
        "# Test\n",
        "phone_numbers = [\"+1 223-456-7890\",\n",
        "                \"1-223-456-7890\",\n",
        "                \"+1 223 456-7890\",\n",
        "                \"(223) 456-7890\",\n",
        "                \"1 223 456 7890\",\n",
        "                \"999.456.7890\",\n",
        "                \"1-989-111-2222\",\n",
        "                \"223.456.7890\"]\n",
        "\n",
        "regexp = re.compile(r\"\\+?(?P<country>\\d{1,3})?[- .]?\\(?(?P<area>\\d{3})\\)?[- .]?(?P<exch>(\\d{3}))[- .](?P<number>\\d{4})\")\n",
        "\n",
        "for number in phone_numbers:\n",
        "    try:\n",
        "      normalized_phone_number = regexp.sub(return_number, number)\n",
        "      print(f'{normalized_phone_number}')\n",
        "    except ValueError as e:\n",
        "      print(f'{number} is invalid: {e}')"
      ],
      "metadata": {
        "id": "pm0UFhH2AHGu",
        "outputId": "2084478d-0d11-4c36-eefb-ce063d37c678",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "1-223-456-7890\n",
            "1-223-456-7890\n",
            "1-223-456-7890\n",
            "1-223-456-7890\n",
            "1-223-456-7890\n",
            "999.456.7890 is invalid: invalid phone number area code 999\n",
            "1-989-111-2222 is invalid: invalid phone number exchange 111\n",
            "1-223-456-7890\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Copilot solution"
      ],
      "metadata": {
        "id": "_J1u27E8WqMU"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Generated by Copilot\n",
        "import re\n",
        "\n",
        "def normalize_phone_number(phone_number):\n",
        "    phone_number = re.sub(r'\\D', '', phone_number)\n",
        "\n",
        "    if len(phone_number) not in [10, 11]:\n",
        "        raise ValueError(\"Invalid phone number length\")\n",
        "\n",
        "    if not re.match(r'^[2-9][0-8][0-9][2-9]', phone_number[-10:]):\n",
        "        raise ValueError(\"Invalid area code or exchange code\")\n",
        "\n",
        "    if len(phone_number) == 10:\n",
        "        phone_number = '1' + phone_number\n",
        "\n",
        "    phone_number = re.sub(r'(\\d{1})(\\d{3})(\\d{3})(\\d{4})', r'\\1-\\2-\\3-\\4', phone_number)\n",
        "\n",
        "    return phone_number\n",
        "\n",
        "# Test the function.\n",
        "phone_numbers = [\"+1 223-456-7890\",\n",
        "                \"1-223-456-7890\",\n",
        "                \"+1 223 456-7890\",\n",
        "                \"(223) 456-7890\",\n",
        "                \"1 223 456 7890\",\n",
        "                \"999.456.7890\",\n",
        "                \"1-989-111-2222\",\n",
        "                \"223.456.7890\"]\n",
        "\n",
        "for number in phone_numbers:\n",
        "    try:\n",
        "      normalized_phone_number = normalize_phone_number(number)\n",
        "      print(f'{normalized_phone_number}')\n",
        "    except ValueError as e:\n",
        "      print(f'{number} is invalid: {e}')"
      ],
      "metadata": {
        "id": "waiBD7ayBhcp",
        "outputId": "41a70018-7776-4823-a3d2-9cd13ad3ff4e",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "1-223-456-7890\n",
            "1-223-456-7890\n",
            "1-223-456-7890\n",
            "1-223-456-7890\n",
            "1-223-456-7890\n",
            "999.456.7890 is invalid: Invalid area code or exchange code\n",
            "1-989-111-2222 is invalid: Invalid area code or exchange code\n",
            "1-223-456-7890\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Prompt:\n",
        "\n",
        "```\n",
        "Using regular expressions, write code to normalize phone numbers in\n",
        "formats (NNN) NNN-NNNN, NNN-NNN-NNNN, NNN NNN-NNNN, NNN.NNN.NNNN, and NNN NNN\n",
        "NNNN (with or without a country code and a +) to 1-NNN-NNN-NNNN. Also raise a\n",
        "ValueError if the first digit of the area code and the exchange code is not\n",
        "2-9, or the second digit of the area code is 9.\n",
        "```\n"
      ],
      "metadata": {
        "id": "7-JvnNVAT3HW"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Colaboratory solution"
      ],
      "metadata": {
        "id": "lYnk4r-2WgN9"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# prompt: Write the code to normalize phone numbers as specified in the cell above.\n",
        "\n",
        "import re\n",
        "\n",
        "def normalize_phone_number(phone_number):\n",
        "  \"\"\"\n",
        "  Normalizes a phone number to the format 1-NNN-NNN-NNNN.\n",
        "\n",
        "  Args:\n",
        "    phone_number: The phone number to normalize.\n",
        "\n",
        "  Returns:\n",
        "    The normalized phone number.\n",
        "\n",
        "  Raises:\n",
        "    ValueError: If the phone number is not in a valid format.\n",
        "  \"\"\"\n",
        "\n",
        "  # Remove all non-digit characters.\n",
        "  phone_number = re.sub(r'\\D', '', phone_number)\n",
        "\n",
        "  # Check if the phone number is in a valid format.\n",
        "  if not re.match(r'^(1)?\\d{10}$', phone_number):\n",
        "    raise ValueError('Invalid phone number format.')\n",
        "\n",
        "  # Check if the first digit of the area code and the exchange code is not 2-9, or the second digit of the area code is 9.\n",
        "  if not re.match(r'^1?[2-9]\\d{2}[2-9]\\d{6}$', phone_number):\n",
        "    raise ValueError('Invalid area code or exchange code.')\n",
        "\n",
        "  # Normalize the phone number to the format 1-NNN-NNN-NNNN.\n",
        "  return '-'.join([phone_number[0:1], phone_number[1:4], phone_number[4:7], phone_number[7:]])\n",
        "\n",
        "# Test the function.\n",
        "phone_numbers = [\"+1 223-456-7890\",\n",
        "                \"1-223-456-7890\",\n",
        "                \"+1 223 456-7890\",\n",
        "                \"(223) 456-7890\",\n",
        "                \"1 223 456 7890\",\n",
        "                \"999.456.7890\",\n",
        "                \"1-989-111-2222\",\n",
        "                \"223.456.7890\"]\n",
        "\n",
        "for number in phone_numbers:\n",
        "    try:\n",
        "      normalized_phone_number = normalize_phone_number(number)\n",
        "      print(f'{normalized_phone_number}')\n",
        "    except ValueError as e:\n",
        "      print(f'{number} is invalid: {e}')"
      ],
      "metadata": {
        "id": "ga9ELmaFWaXQ",
        "outputId": "e141eed0-3240-48ab-c157-dcb281882e71",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "1-223-456-7890\n",
            "1-223-456-7890\n",
            "1-223-456-7890\n",
            "2-234-567-890\n",
            "1-223-456-7890\n",
            "9-994-567-890\n",
            "1-989-111-2222 is invalid: Invalid area code or exchange code.\n",
            "2-234-567-890\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [],
      "metadata": {
        "id": "Y2k1QKX8_uLC"
      },
      "execution_count": null,
      "outputs": []
    }
  ]
}